#pragma once

#pragma pack(push, 8)
#include <boost/sml.hpp>
#include <cassert>
#include <iostream>
#pragma pack(pop)

#include "../SharedData.h"
#include "DriverData.h"

namespace Driver {

namespace sml = boost::sml;
// event
struct TickEvent {};
struct FaultResetEvent{};
struct ShutdownEvent{};
struct SwitchOnEvent{};
struct DisableVoltageEvent{};
struct QuickStopEvent{};
struct EnableVoltageEvent{};
struct DisableOperationEvent{};
struct EnableOperationEvent{};
struct ReadyToSwitchOnEvent{};
struct HomeEvent{};
struct MoveEvent{};
struct StopEvent{};
struct SlowerEvent{};
struct FasterEvent{};
struct HaltHomeEvent{};

struct HomeState {
    // state
    struct HomeDisabledState{};
    struct ReadyToHomeState{};
    struct HomingState{};
    struct HomeFinishedState{};
    struct HomeHaltedState{};
    struct SwitchToHomeErrorState{};
    struct HomeErrorState{};
    auto operator()(){
        using namespace sml;
        // state
        auto home_disabled_s = state<HomeDisabledState>;
        auto ready_to_home_s = state<ReadyToHomeState>;
        auto homing_s = state<HomingState>;
        auto home_finished_s = state<HomeFinishedState>;
        auto home_halted_s = state<HomeHaltedState>;
        auto switch_to_home_error_s = state<SwitchToHomeErrorState>;
        auto home_error_s = state<HomeErrorState>;
        // event
        auto halt_home_e = event<HaltHomeEvent>;
        auto tick_e = event<TickEvent>;
        // guard
        auto home_start_g = [](const DriverData& driver_data) { return driver_data.IsHomeStarted(); };
        auto home_finish_g = [](const DriverData& driver_data) { return driver_data.IsHomeFinished(); };
        auto home_halt_g = [](const DriverData& driver_data) { return driver_data.IsHomeHalted(); };
        auto switch_to_home_error_g = [](const DriverData& driver_data) { return driver_data.IsSwitchToHomeErrored(); };
        auto home_error_g = [](const DriverData& driver_data) { return driver_data.IsHomeErrored(); };
        // action
        auto set_home_para_a = [](DriverData& driver_data) { driver_data.SetHomePara(); };
        auto home_start_a = [](DriverData& driver_data) { driver_data.StartHome(); };
        auto home_halt_a = [](DriverData& driver_data) { driver_data.CancelHome(); };
        auto exit_home_a = [](DriverData& driver_data) { driver_data.ExitHome(); };
        auto entry_home_disabled_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::HomeDisabledState State" << std::endl;
        };
        auto entry_ready_to_home_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::ReadyToHomeState State" << std::endl;
        };
        auto entry_homing_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::HomingState State" << std::endl;
        };
        auto entry_home_finished_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::HomeFinishedState State" << std::endl;
        };
        auto entry_home_halted_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::HomeHaltedState State" << std::endl;
        };
        auto entry_switch_to_home_error_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::SwitchToHomeErrorState State" << std::endl;
        };
        auto entry_home_error_a = []() {
            std::cout << "Entry NormalState::OperationEnabledState::HomeState::HomeErrorState State" << std::endl;
        };
        // clang-format off
        return make_transition_table(
            *home_disabled_s            + sml::on_entry<_> / (set_home_para_a, entry_home_disabled_a),
            home_disabled_s             + tick_e                                    = ready_to_home_s,
            ready_to_home_s             + sml::on_entry<_> / (home_start_a, entry_ready_to_home_a),
            ready_to_home_s             + tick_e [home_start_g]                     = homing_s,
            homing_s                    + sml::on_entry<_> / entry_homing_a,
            homing_s                    + halt_home_e / home_halt_a,
            homing_s                    + tick_e [home_finish_g]                    = home_finished_s,
            // homing_s                    + tick_e [home_halt_g]                      = home_halted_s,
            homing_s                    + tick_e [switch_to_home_error_g]           = switch_to_home_error_s,
            homing_s                    + tick_e [home_error_g]                     = home_error_s,
            home_finished_s             + sml::on_entry<_> / entry_home_finished_a,
            home_finished_s             + tick_e / exit_home_a                      = X,
            home_halted_s               + sml::on_entry<_> / entry_home_halted_a,
            home_halted_s               + tick_e / exit_home_a                      = X,
            home_error_s                + sml::on_entry<_> / entry_home_error_a,
            home_error_s                + tick_e / exit_home_a                      = X,
            switch_to_home_error_s      + sml::on_entry<_> / entry_switch_to_home_error_a,
            switch_to_home_error_s      + tick_e [home_error_g]                     = home_error_s
        );
        // clang-format on
    };
};

struct OperationEnabledState {
    // state
    struct StandStillState{};
    struct MovingState{};
    auto operator()() {
        using namespace sml;
        // state
        auto stand_still_s = state<StandStillState>;
        auto moving_s = state<MovingState>;
        auto home_s = state<HomeState>;
        // event
        auto home_e = event<HomeEvent>;
        auto move_e = event<MoveEvent>;
        auto stop_e = event<StopEvent>;
        auto faster_e = event<FasterEvent>;
        auto slower_e = event<SlowerEvent>;
        auto tick_e = event<TickEvent>;
        // guard
        auto home_g = [](const DriverData& driver_data) { return driver_data.IsHomeMode(); };
        auto move_g = [](const DriverData& driver_data) { return driver_data.IsCspMode(); };
        // action
        auto home_a = [](DriverData& driver_data) { driver_data.ChangeToHomeMode(); };
        auto csp_a = [](DriverData& driver_data) { driver_data.ChangeToCspMode(); };
        auto faster_a = []() {};
        auto slower_a = []() {};
        auto move_a = []() {
            uint32_t position {};
            position += 100;
        };
        auto entry_stand_still_a = []() { std::cout << "Entry NormalState::OperationEnabledState::StandStillState State" << std::endl;};
        auto entry_moving_a = []() { std::cout << "Exit NormalState::OperationEnabledState::MovingState State" << std::endl;};
        auto entry_home_a = []() { std::cout << "Entry NormalState::OperationEnabledState::HomeState State" << std::endl;};
        // clang-format off
        return make_transition_table(
            *stand_still_s  + home_e / home_a,
            stand_still_s   + sml::on_entry<_> / entry_stand_still_a,
            stand_still_s   + move_e                    = moving_s,
            stand_still_s   + tick_e [home_g]           = home_s,
            moving_s        + sml::on_entry<_> / entry_moving_a,
            moving_s        + faster_e / faster_a,
            moving_s        + slower_e / slower_a,
            moving_s        + tick_e / move_a,
            moving_s        + stop_e                    = home_s,
            home_s          + sml::on_entry<_> / entry_home_a,
            home_s          + tick_e [move_g]           = stand_still_s
        );
        // clang-format on
    };
};

struct NormalState {
    // state
    struct SwitchOnDisabledState;
    struct ReadyToSwitchOnState;
    struct SwitchOnState;
    struct QuickStopActiveState;

    auto operator()() {
        using namespace sml;
        // state
        auto switch_on_disabled_s = state<SwitchOnDisabledState>;
        auto ready_to_switch_on_s = state<ReadyToSwitchOnState>;
        auto switch_on_s = state<SwitchOnState>;
        auto quick_stop_active_s = state<QuickStopActiveState>;
        auto operation_enabled_s = state<OperationEnabledState>;
        // event
        auto tick_e = event<TickEvent>;
        auto shutdown_e = event<ShutdownEvent>;
        auto switch_on_e = event<SwitchOnEvent>;
        auto disable_voltage_e = event<DisableVoltageEvent>;
        auto enable_operation_e = event<EnableOperationEvent>;
        auto disable_operation_e = event<DisableOperationEvent>;
        auto quick_stop_e = event<QuickStopEvent>;
        // guard
        auto ready_to_switch_on_g = [](const DriverData& driver_data) { return driver_data.IsReadyToSwitchOn(); };
        auto switch_on_g = [](const DriverData& driver_data) { return driver_data.IsSwitchedOn(); };
        auto switch_on_disabled_g = [](const DriverData& driver_data) { return driver_data.IsSwitchOnDisabled(); };
        auto enable_operation_g = [](const DriverData& driver_data) { return driver_data.IsOperationEnabled(); };
        auto quick_stop_g = [](const DriverData& driver_data) { return driver_data.IsQuickStoped(); };
        // Normal aciton
        auto shutdown_a = [](DriverData& driver_data) {
            driver_data.ShutDown();
        };
        auto switch_on_a = [](DriverData& driver_data) { driver_data.SwitchOn(); };
        auto disable_voltage_a = [](DriverData& driver_data) { driver_data.DisableVoltage(); };
        auto enable_operation_a = [](DriverData& driver_data) {
            driver_data.EnableOperation();
            driver_data.SyncPostion();
        };
        auto disable_operation_a = [](DriverData& driver_data) { driver_data.DisableOperation(); };
        auto quick_stop_a = [](DriverData& driver_data) { driver_data.QuickStop(); };
        auto entry_switch_on_disabled_a = []() { std::cout << "Entry Normal::SwitchOnDisabledState State" << std::endl;};
        auto entry_ready_to_switch_on_a = []() { std::cout << "Entry Normal::ReadyToSwitchOnState State" << std::endl;};
        auto entry_switch_on_a = []() { std::cout << "Entry Normal::SwitchOnState State" << std::endl;};
        auto entry_quick_stop_active_a = []() { std::cout << "Entry Normal::QuickStopActiveState State" << std::endl;};
        auto entry_operation_enabled_a = []() { std::cout << "Entry Normal::OperationEnabledState State" << std::endl;};
        // clang-format off
        return make_transition_table(
            *switch_on_disabled_s   + sml::on_entry<_> / entry_switch_on_disabled_a,
            ready_to_switch_on_s    + sml::on_entry<_> / entry_ready_to_switch_on_a,
            switch_on_s             + sml::on_entry<_> / entry_switch_on_a,
            operation_enabled_s     + sml::on_entry<_> / entry_operation_enabled_a,
            quick_stop_active_s     + sml::on_entry<_> / entry_quick_stop_active_a,
            switch_on_disabled_s    + shutdown_e / shutdown_a,
            switch_on_disabled_s    + tick_e [ready_to_switch_on_g]                 = ready_to_switch_on_s,
            ready_to_switch_on_s    + switch_on_e / switch_on_a,
            ready_to_switch_on_s    + disable_voltage_e / disable_voltage_a,
            ready_to_switch_on_s    + tick_e [switch_on_g]                          = switch_on_s,
            ready_to_switch_on_s    + tick_e [switch_on_disabled_g]                 = switch_on_disabled_s,
            switch_on_s             + enable_operation_e / enable_operation_a,
            switch_on_s             + shutdown_e / shutdown_a,
            switch_on_s             + disable_voltage_e / disable_voltage_a,
            switch_on_s             + tick_e [enable_operation_g]                   = operation_enabled_s,
            switch_on_s             + tick_e [ready_to_switch_on_g]                 = ready_to_switch_on_s,
            switch_on_s             + tick_e [switch_on_disabled_g]                 = switch_on_disabled_s,
            operation_enabled_s     + shutdown_e / shutdown_a,
            operation_enabled_s     + disable_voltage_e / disable_voltage_a,
            operation_enabled_s     + disable_operation_e / disable_operation_a,
            operation_enabled_s     + quick_stop_e / quick_stop_a,
            operation_enabled_s     + tick_e [ready_to_switch_on_g]                 = ready_to_switch_on_s,
            operation_enabled_s     + tick_e [switch_on_disabled_g]                 = switch_on_disabled_s,
            operation_enabled_s     + tick_e [switch_on_g]                          = switch_on_s,
            operation_enabled_s     + tick_e [quick_stop_g]                         = quick_stop_active_s,
            quick_stop_active_s     + enable_operation_e / enable_operation_a,
            quick_stop_active_s     + disable_voltage_e / disable_voltage_a,
            quick_stop_active_s     + tick_e [enable_operation_g]                   = operation_enabled_s,
            quick_stop_active_s     + tick_e [switch_on_disabled_g]                 = switch_on_disabled_s
        );
        // clang-format on
    };
};

struct TransitionTable {
    // state
    struct NotReadyToSwitchOnState;
    struct FaultReactionActiveState;
    struct FaultState;

    auto operator()() const noexcept {
        using namespace sml;
        // state
        auto not_ready_to_switch_on_s = state<NotReadyToSwitchOnState>;
        auto normal_s = state<NormalState>;
        auto fault_reaction_active_s = state<FaultReactionActiveState>;
        auto fault_s = state<FaultState>;
        // event
        auto tick_e = event<TickEvent>;
        auto fault_reset_e = event<FaultResetEvent>;
        // guard
        auto error_g = [](const DriverData& driver_data) { return driver_data.IsErrored(); };
        auto switch_on_disable_g = [](const DriverData& driver_data) { return driver_data.IsSwitchOnDisabled(); };
        // action
        auto reset_a = [](DriverData& driver_data) { driver_data.Reset(); };
        auto entry_not_ready_to_switch_on_a = []() { std::cout << "Entry NotReadyToSwitchOnState State" << std::endl; };
        auto entry_fault_a = []() { std::cout << "Entry Fault State" << std::endl; };
        auto entry_normal_a = []() { std::cout << "Entry Normal State" << std::endl; };
        // clang-format off
        return make_transition_table(
            *not_ready_to_switch_on_s   + tick_e [switch_on_disable_g]                          = normal_s,
            not_ready_to_switch_on_s    + sml::on_entry<_> / entry_not_ready_to_switch_on_a,
            normal_s                    + tick_e [error_g]                                      = fault_reaction_active_s,
            normal_s                    + sml::on_entry<_> / entry_normal_a,
            fault_reaction_active_s     + tick_e                                                = fault_s,
            fault_s                     + fault_reset_e / reset_a,
            fault_s                     + sml::on_entry<_> / entry_fault_a,
            fault_s                     + tick_e [switch_on_disable_g]                          = normal_s
        );
        // clang-format on
    };
};

}  // namespace sml